package com.mcarthur.countdown.client.renderer;

import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.gwt.event.dom.client.LoadEvent;
import com.google.gwt.event.dom.client.LoadHandler;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.googlecode.gwtgl.array.Float32Array;
import com.googlecode.gwtgl.binding.WebGLBuffer;
import com.googlecode.gwtgl.binding.WebGLCanvas;
import com.googlecode.gwtgl.binding.WebGLProgram;
import com.googlecode.gwtgl.binding.WebGLRenderingContext;
import com.googlecode.gwtgl.binding.WebGLShader;
import com.googlecode.gwtgl.binding.WebGLTexture;
import com.googlecode.gwtgl.binding.WebGLUniformLocation;
import com.mcarthur.countdown.client.Shaders;
import com.mcarthur.countdown.client.drawable.Ball;
import com.mcarthur.countdown.client.drawable.CountdownClock;
import com.mcarthur.countdown.client.images.Images;
import com.mcarthur.countdown.client.utils.Matrix;

public class CountdownRenderer implements Renderer, ResizeHandler {
	public static final float WIDTH_RATIO = 1.25f;
	public static float viewWidth;
	public static float viewHeight;
	
	private static final int RENDER_INTERVAL = 20;//33
	
	public static WebGLProgram shaderProgram;
    public static int vertexPositionAttribute;
    public static int textureCoordAttribute;
    
    public static WebGLBuffer vertexBuffer;
	private WebGLCanvas canvas;
	public static WebGLRenderingContext gl;
	private float[] orthoMatrix = new float[16];
	private Timer renderTimer;
	private Logger logger = Logger.getLogger("");
	private CountdownClock clock;
	private Label fpsCounter;
	private static int FPS_DELAY = 200;
	public static WebGLTexture texture;
	public static WebGLUniformLocation textureUniform;
    public static WebGLBuffer vertexTextureCoordBuffer;
    
    public CountdownRenderer(WebGLCanvas canvas, WebGLRenderingContext gl) {
        this.canvas = canvas;
        this.gl = gl;
        renderTimer = new Timer() {
          @Override
            public void run() {
                drawScene();
            }  
        };
        float posX = (CountdownClock.WIDTH * (WIDTH_RATIO - 1)) / 2.0f;
        clock = new CountdownClock(posX);
//        fpsCounter = new Label();
//        fpsCounter.getElement().setId("fps");
//        RootPanel.get().add(fpsCounter);
        
        Window.enableScrolling(false);
        Window.addResizeHandler(this);

        // Unneeded for 2d
        gl.disable(WebGLRenderingContext.DITHER);
        gl.disable(WebGLRenderingContext.CULL_FACE);
        gl.disable(WebGLRenderingContext.DEPTH_TEST);

        //initTexture();
    }

    @Override
    public void onResize(ResizeEvent event) {
        doResize(event.getWidth(), event.getHeight());
    }
    
    private void doResize(int width, int height) {
        canvas.setWidth(width + "px");
        canvas.setHeight(height + "px");
        gl.viewport(0, 0, width, height);

        float ratio = (float) height / width;
        viewWidth = CountdownClock.WIDTH * WIDTH_RATIO;
        viewHeight = viewWidth * ratio;

        Matrix.orthoM(orthoMatrix, 0, 0, viewWidth, 0, viewHeight, -1, 1);
        WebGLUniformLocation uniformLocation = gl.getUniformLocation(shaderProgram, "perspectiveMatrix");
        gl.uniformMatrix4fv(uniformLocation, false, orthoMatrix);

        clock.setPosY((viewHeight - CountdownClock.HEIGHT) / 2.0f);
    }
    
    public void start() {
        initShaders();
        gl.clearColor(1.0f, 1.0f, 1.0f, 1.0f);
        initBuffers();

        doResize(Window.getClientWidth(), Window.getClientHeight());
        renderTimer.scheduleRepeating(RENDER_INTERVAL);
    }
    
    public void initShaders() {
        WebGLShader fragmentShader = getShader(WebGLRenderingContext.FRAGMENT_SHADER, Shaders.INSTANCE.fragmentShader()
                .getText());
        WebGLShader vertexShader = getShader(WebGLRenderingContext.VERTEX_SHADER, Shaders.INSTANCE.vertexShader()
                .getText());

        shaderProgram = gl.createProgram();
        gl.attachShader(shaderProgram, vertexShader);
        gl.attachShader(shaderProgram, fragmentShader);
        gl.linkProgram(shaderProgram);

        if (!gl.getProgramParameterb(shaderProgram, WebGLRenderingContext.LINK_STATUS)) {
            throw new RuntimeException("Could not initialise shaders");
        }

        gl.useProgram(shaderProgram);
        
        vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "vertexPosition");
        gl.enableVertexAttribArray(vertexPositionAttribute);
        
        WebGLUniformLocation scaleVectorLoc = gl.getUniformLocation(CountdownRenderer.shaderProgram, "scaleVector");
        gl.uniform2f(scaleVectorLoc, Ball.WIDTH, Ball.WIDTH);
        
        Ball.transVectorLoc = gl.getAttribLocation(shaderProgram, "transVector");
        gl.enableVertexAttribArray(Ball.transVectorLoc);
        Ball.colorLoc = gl.getAttribLocation(shaderProgram, "v_color");
        
//        textureCoordAttribute = gl.getAttribLocation(shaderProgram, "texPosition");
//        gl.enableVertexAttribArray(textureCoordAttribute);
//
//        // get the position of the tex uniform.
//        textureUniform = gl.getUniformLocation(shaderProgram, "tex");
    }

    private WebGLShader getShader(int type, String source) {
        WebGLShader shader = gl.createShader(type);

        gl.shaderSource(shader, source);
        gl.compileShader(shader);

        if (!gl.getShaderParameterb(shader, WebGLRenderingContext.COMPILE_STATUS)) {
            throw new RuntimeException(gl.getShaderInfoLog(shader));
        }

        return shader;
    }

    private void initBuffers() {
        vertexBuffer = gl.createBuffer();
        gl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, vertexBuffer);

        float[] vertices = new float[] { 
                0.0f, 0.0f, 0.0f,
                0.0f, 1.0f, 0.f,
                1.0f, 0.0f, 0.0f,
                1.0f, 1.0f, 0.0f
                                         
        };
        gl.bufferData(WebGLRenderingContext.ARRAY_BUFFER, Float32Array.create(vertices),
                WebGLRenderingContext.STATIC_DRAW);
        
        gl.vertexAttribPointer(vertexPositionAttribute, 3, WebGLRenderingContext.FLOAT, false, 0, 0);
        
        // Texture buffer
//        vertexTextureCoordBuffer = gl.createBuffer();
//        gl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, vertexTextureCoordBuffer);
//        float[] texCoords = {0.f, 0.f,
//                             0.f, 1.f,
//                             1.f, 0.f,
//                             1.f, 1.f};
//        gl.bufferData(WebGLRenderingContext.ARRAY_BUFFER, Float32Array.create(texCoords), WebGLRenderingContext.STATIC_DRAW);
    }

    private long lastDraw;
    private long lastFPS;
    private void drawScene() {
//        long time = System.currentTimeMillis();
//        long diff = time - lastDraw;
//        if (diff <= 0) {
//            diff = 1;
//        }
//        long fps = 1000 / diff;
//        if (time - lastFPS >= FPS_DELAY) {
//            fpsCounter.setText(String.valueOf(fps));
//            lastFPS = time;
//        }
//
//        lastDraw = time;
        
        gl.clear(WebGLRenderingContext.COLOR_BUFFER_BIT);
        clock.draw(gl);
        
        checkError();
    }
    
//    private void initTexture() {
//            texture = gl.createTexture();
//            gl.bindTexture(WebGLRenderingContext.TEXTURE_2D, texture);
//            final Image img = getImage(Images.INSTANCE.ball());
//            img.addLoadHandler(new LoadHandler() {
//                    @Override
//                    public void onLoad(LoadEvent event) {
//                            RootPanel.get().remove(img);
//                            
//                            gl.bindTexture(WebGLRenderingContext.TEXTURE_2D, texture);
//                            gl.texImage2D(WebGLRenderingContext.TEXTURE_2D, 0, WebGLRenderingContext.RGBA, WebGLRenderingContext.RGBA, WebGLRenderingContext.UNSIGNED_BYTE, img.getElement());
//                            start();
//                    }
//            });
//            gl.texParameteri(WebGLRenderingContext.TEXTURE_2D, WebGLRenderingContext.TEXTURE_MAG_FILTER, WebGLRenderingContext.LINEAR);
//            gl.texParameteri(WebGLRenderingContext.TEXTURE_2D, WebGLRenderingContext.TEXTURE_MIN_FILTER, WebGLRenderingContext.LINEAR);
//            gl.bindTexture(WebGLRenderingContext.TEXTURE_2D, null);
//            //checkError();
//    }
//    
//    /**
//     * Converts ImageResource to Image.
//     * @param imageResource
//     * @return {@link Image} to be used as a texture
//     */
//    public Image getImage(final ImageResource imageResource) {
//            final Image img = new Image();
//            img.setVisible(false);
//            RootPanel.get().add(img);
//
//            img.setUrl(imageResource.getURL());
//    
//            return img;
//    }
    
    private void checkError() {
        int error = gl.getError();
        if (error != WebGLRenderingContext.NO_ERROR) {
                String message = "WebGL Error: " + error;
                logger.log(Level.SEVERE, message);
                logger.log(Level.INFO, "invalid op = " + gl.INVALID_OPERATION);
                throw new RuntimeException(message);
        }
    }
}
